import Dep, { popTarget, pushTarget } from "./dep";

let id = 0;
class Watcher {
  constructor(vm, exprOrFn, options, cb) {
    this.id = id++;
    this.renderWatcher = options; //是否是更新watcher
    if (typeof exprOrFn === "string") {
      this.getter = function () {
        return vm[exprOrFn];
      };
    } else {
      this.getter = exprOrFn; //getter意味着调用这个函数可以发生取值操作
    }
    this.deps = []; //后续实现计算属性和一些清理工作
    this.depsId = new Set();
    this.vm = vm;
    this.cb = cb;
    this.user = options.user; //是否是用户自己的watcher
    this.lazy = options.lazy;
    this.dirty = this.lazy;
    this.value = this.lazy ? undefined : this.get();
  }
  addDep(dep) {
    //重复的属性不需要记录
    let id = dep.id;
    if (!this.depsId.has(id)) {
      this.deps.push(dep);
      this.depsId.add(id);
      dep.addSub(this);
    }
  }
  evaluate() {
    //获取用户函数返回值，并且标志未脏
    this.value = this.get();
    this.dirty = false;
  }
  get() {
    // Dep.target = this;
    pushTarget(this);
    let value = this.getter.call(this.vm); //去vm上取值
    // Dep.target = null; //渲染完就清空
    popTarget();
    return value;
  }
  depend() {
    let i = this.deps.length;
    while (i--) {
      this.deps[i].depend();
    }
  }
  update() {
    // 如果是计算属性，依赖的值变了，就标志计算属性是脏值
    if (this.lazy) {
      this.dirty = true;
    } else {
      queueWatcher(this); //把当前的watcher暂存起来
      // this.get(); //重新渲染
    }
  }
  run() {
    let oldValue = this.value;
    let newValue = this.get();
    if (this.user) {
      this.cb.call(this.vm, newValue, oldValue);
    }
  }
}

let queue = [];
let has = {};
let pending = false; //防抖
function queueWatcher(watcher) {
  const id = watcher.id;
  if (!has[id]) {
    queue.push(watcher);
    has[id] = true;
    // 不管执行多少次update，最终只刷新一次
    if (!pending) {
      nextTick(flushSchedulerQueue, 0);
      pending = true;
    }
    // console.log("queue", queue);
  }
}
let callbacks = [];
let waiting = false;
function flashCallbacks() {
  let cbs = callbacks.slice(0);
  waiting = false;
  callbacks = [];
  cbs.forEach((cb) => cb());
}
//nextTick没有直接使用某个api，而是采用优雅降级的方式
// 内部先采用promise（ie不兼容），MutationObserver（h5api），可以考虑ie的setmeditate, setTimeout
let timeFunc;
if (Promise) {
  timeFunc = () => {
    Promise.resolve().then(flashCallbacks);
  };
} else if (MutationObserver) {
  let observer = new MutationObserver(flashCallbacks);
  let textNode = document.createTextNode(1);
  observer.observe(textNode, {
    characterData: true,
  });
  timeFunc = () => {
    textNode.textContent = 2;
  };
} else if (setImmediate) {
  timeFunc = () => {
    setImmediate(flashCallbacks);
  };
} else {
  timeFunc = () => {
    setTimeout(() => {
      flashCallbacks();
    }, 0);
  };
}
export function nextTick(cb) {
  callbacks.push(cb);
  if (!waiting) {
    // setTimeout(() => {
    //   flashCallbacks();
    // }, 0);
    timeFunc();
    waiting = true;
  }
}
function flushSchedulerQueue() {
  let flushQueue = queue.slice(0);
  queue = [];
  has = {};
  pending = false;
  //刷新过程中可能有新的watcher，重新放到queue中
  flushQueue.forEach((q) => q.run());
}
export default Watcher;
